﻿Imports System.Reflection
Imports System.Collections.Specialized
Imports System.Collections.ObjectModel

''' <summary>
''' オブザーバブルコレクションファクトリー
''' </summary>
''' <remarks>
''' #102 LazyListをObservableCollection型継承に変える
''' </remarks>
Public Class ObservableCollectionFactory

    Public Function Create(instance As Object, rel As ChildRelation) As IList
        Dim lst As IList = Me.CreateCollection(instance, rel)
        Return Me.SetCollectionChangeEvent(instance, lst, rel)
    End Function

    Protected Function CreateCollection(instance As Object, rel As ChildRelation) As IList
        Dim t As Type = rel.CollectionProperty.PropertyType.GetGenericArguments.First

        '方法 : リフレクションを使用してジェネリック型をチェックおよびインスタンス化する
        'http://msdn.microsoft.com/ja-jp/library/b8ytshk6(v=vs.100).aspx
        Dim listT As Type = GetType(ObservableCollection(Of )).MakeGenericType({t})
        Return Activator.CreateInstance(listT)
    End Function

    Protected Function SetCollectionChangeEvent(instance As Object, lst As IList, rel As ChildRelation) As IList
        Dim childProp = rel.CollectionProperty
        Dim childType = childProp.PropertyType.GetGenericArguments.First
        Dim parentProp = rel.YourColumn.PropertyInfo

        '親との関連を強制化する
        '方法 : リフレクションを使用してデリゲートをフックする
        'http://msdn.microsoft.com/ja-jp/library/ms228976(v=vs.100).aspx

        'イベントを表す EventInfo オブジェクトを取得し、EventHandlerType プロパティを使用して、イベントの処理に使用するデリゲートの型を取得します
        Dim eventCollectionChanged As EventInfo = lst.GetType.GetEvent("CollectionChanged")
        Dim delegateType As Type = eventCollectionChanged.EventHandlerType

        'イベントを処理するメソッドを表す MethodInfo オブジェクトを取得します
        Dim target As Object = Nothing

        If PropertyReader.IsExtensionCollection(childProp) Then
            target = New ParentAdderForExtension With {.Parent = instance, .ParentProp = parentProp}
        Else
            target = New ParentAdder With {.Parent = instance, .ParentProp = parentProp}
        End If
        Dim miHandler = target.GetType.GetMethod("OnCollectionChanged")

        'CreateDelegate メソッドを使用して、デリゲートのインスタンスを作成します
        Dim d As [Delegate] = [Delegate].CreateDelegate(delegateType, target, miHandler)

        'add アクセサー メソッドを取得し、このメソッドを呼び出してイベントをフックします
        Dim miAddHandler As MethodInfo = eventCollectionChanged.GetAddMethod()
        Dim addHandlerArgs() As Object = {d}
        miAddHandler.Invoke(lst, addHandlerArgs)

        Return lst
    End Function

    ''' <summary>
    ''' Childコレクション用イベント処理
    ''' </summary>
    ''' <remarks></remarks>
    Private Class ParentAdder
        Property ParentProp As PropertyInfo
        Property Parent As Object
        Sub OnCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
            If e.Action <> NotifyCollectionChangedAction.Add Then Return
            If ParentProp Is Nothing Then Return
            For Each item In e.NewItems
                ParentProp.SetValue(item, Parent, Nothing)
            Next
        End Sub
    End Class

    ''' <summary>
    ''' Extensionコレクション用イベント処理
    ''' </summary>
    ''' <remarks></remarks>
    Private Class ParentAdderForExtension

        Property ParentProp As PropertyInfo

        Property Parent As Object

        Sub OnCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
            If e.Action <> NotifyCollectionChangedAction.Add Then Return
            If ParentProp Is Nothing Then Return

            For Each item In e.NewItems
                If sender.Count > 1 Then
                    sender.Clear()
                    Throw New ManyExtensionException
                Else
                    ParentProp.SetValue(item, Parent, Nothing)
                End If
            Next
        End Sub

    End Class

End Class
